-module(db).
-compile(export_all).

-include_lib("stdlib/include/qlc.hrl").
-include("records.hrl").

-define(MAX_REVS, 50).

%% Helper Levels : 
%% => DB                                      | Level 0 
%% =====> Tables                              | Level 1
%% ============> Records                      | Level 2
%% ===================> Record content        | Level 3


						% Level 0 
routine() -> 
    mnesia:create_schema([node()]),
    mnesia:start().
init() -> 
    mnesia:create_table(code, [ {attributes, record_info(fields, code)}, {disc_copies, [node()]}, {type, ordered_set} ]),
    mnesia:create_table(revision, [{attributes, record_info(fields, revision)}, {disc_copies, [node()]}, {type, ordered_set} ]),
    mnesia:create_table(member, [{attributes, record_info(fields, member)}, {disc_copies, [node()]}, {type, ordered_set} ]).
nitrogen_init() ->
    routine(),
    init(),
    mnesia:stop().


do_qlc(Query) ->
    F = fun() -> qlc:e(Query) end,
    case mnesia:transaction(F) of
	{atomic, Out} -> {ok, Out};
	{aborted, Reason} -> {error, Reason}
    end.
do_mwrite(Record) -> %Handle mnesia write function
    case mnesia:transaction( fun() -> mnesia:write(Record) end ) of
	{aborted, Reason} -> {error, Reason};
	_ -> ok
    end.

						%Level 1
get_next_id(table, Table) ->
    Fun = fun() -> mnesia:last(Table) end,
    case mnesia:transaction(Fun) of
	{aborted, _} -> error;
        {atomic, '$end_of_table'} -> {ok, 0};
	{atomic, ID} -> {ok, (ID + 1) }
    end.

get_by_id(code, ID) ->
    do_qlc( qlc:q( [ X || X <- mnesia:table(code),
			  ID == X#code.id] )); 
get_by_id(revision, ID) ->
    do_qlc( qlc:q( [ X || X <- mnesia:table(revision),
			  ID == X#revision.id] ));
get_by_id(member, ID) ->
    do_qlc( qlc:q( [ X || X <- mnesia:table(member),
			  ID == X#member.id] )).
get_by_name(member, Name) ->
    do_qlc( qlc:q( [ X || X <- mnesia:table(member),
			  Name == X#member.nick] )).

						%Level 2
check_name(Name, MaintainerID) ->
    Query =  do_qlc(qlc:q([ X || X <- mnesia:table(code),
				 X#code.maintainer_id == MaintainerID,
				 X#code.cname == Name])),
    case Query of
	{ok, []} -> ok; %Available
	_ -> error
    end.

check_nick(Nick) ->
    Query =  do_qlc(qlc:q([ X || X <- mnesia:table(member),
				 X#member.nick == Nick])),
    case Query of
	{ok, []} -> ok; %Available

	_ -> error
    end.

						%Level 3
rids_add(Array, Set, Size, Max) -> %rids = Revision IDs						
    MaxP1 = Max + 1,
    case Size of
	MaxP1 ->
	    rids_loop(Array, Set, 0, Max);
	_ ->
	    {ok, array:set( Size, Set, Array )}
    end.
rids_loop(Array, Set, Acc, MaxSize) ->
    if
	Acc == MaxSize ->
	    {extended, array:set(Acc + 1, Set, Array)};
	true ->
	    Rev = get_by_id(revision, array:get(Acc, Array) ),
	    LockedStatus = Rev#revision.locked,
	    case LockedStatus of
		true -> rids_loop(Array, Set, Acc + 1, MaxSize);
		_ -> {ok, array:set(Acc, Set, Array)}
	    end
    end.

						%Exported
add(revision, {Error, Locked, Code})->
    {ok, ID} = get_next_id(table, revision),
    do_mwrite(#revision{ id = ID,
			 time = now(),
			 error = Error,
			 locked = Locked,
			 code = Code } ),
    {ok, ID};

add(code, {CName, MaintainerID}) ->
    case check_name(CName, MaintainerID) of 
	ok ->
	    {ok, ID} = get_next_id(table, code),
	    do_mwrite(#code{ id = ID,
			     cname = CName,
			     maintainer_id = MaintainerID,
			     max_revs = ?MAX_REVS,
			     revision_ids = array:new() } ),
	    {ok, ID};
	error ->
	    {error, bad_cname}
    end;

add(member, {Nick, Password, Email}) ->
    {ok, ID} = get_next_id(table, member),
    HashedPass = sha2:hexdigest512(Password),

    case check_nick(Nick) of
	ok ->
	    do_mwrite(#member{ id = ID,
			       nick = Nick,
			       password = HashedPass,
			       email = Email,
			       status = "M"}),
	    {ok, ID};
	error ->
	    {error, bad_nick}
    end.

bind(CodeID, RevisionID) ->
    {ok, [CodeObj]} = get_by_id(code, CodeID),
    CodeRevIDs = CodeObj#code.revision_ids,

    CRIMaxRevs = CodeObj#code.max_revs,
    CRISize = array:size(CodeRevIDs),

    case rids_add(CodeRevIDs, RevisionID, CRISize, CRIMaxRevs) of
	{ok, Out} -> do_mwrite( CodeObj#code{revision_ids=Out} );
	{extended, Out} -> do_mwrite( CodeObj#code{max_revs=(CRIMaxRevs+1), revision_ids=Out} )
    end.

%Ugly modification code... yuck.
modify_error(ID, Error) ->
    Revision = get_by_id(revision, ID),
    do_mwrite( Revision#revision{error=Error}).
modify_locked(ID, Locked) ->
    Revision = get_by_id(revision, ID),
    do_mwrite( Revision#revision{locked=Locked}).
modify_code(ID, Code) ->
    Revision = get_by_id(revision, ID),
    do_mwrite( Revision#revision{time=now(), code=Code}).
modify_max_revs(ID, MaxRevs) ->
    Code = get_by_id(code, ID),
    do_mwrite( Code#code{max_revs=MaxRevs}).
modify_name(ID, CName) ->
    Code = get_by_id(code, ID),
    do_mwrite( Code#code{cname=CName}).

modify_member(ID, Nick, Password, Email, Status) ->
    HashedPass = sha2:hexdigest512(Password),
    do_mwrite(#member{ id = ID,
		       nick = Nick,
		       password = HashedPass,
		       email = Email ,
		       status = Status }).
validate_member(id, {ID, HashedPass, Status}) ->
    case get_by_id(member,ID) of
	{ok, [Out]} -> (Out#member.password == HashedPass) and (Out#member.status == Status);
	_ -> false
    end;

validate_member(name, {Nick, HashedPass, Status}) -> 
    case get_by_name(member,Nick) of
	{ok, [Out]} ->
	 	    (Out#member.password == HashedPass) and (Out#member.status == Status);
	_ -> false
    end;

validate_member(name, {Nick, HashedPass}) -> 
    case get_by_name(member,Nick) of
	{ok, [Out]} -> {Out#member.password == HashedPass, Out#member.status};
	_ -> {false, not_found}
    end.
